Oracle 中的分析函数基于对数据行的分组来计算相关值,类似于聚合函数。其和聚合函数主要的区别在于:分析函数对于每个分组返回多行数据,而聚合函数每个分组只能对应一行数据。
分析函数由三个部分组成:分区子句、排序子句和开窗子句,基本语法如下:
1 | function(arg1,arg2,...argN) |
开窗子句windowing-clause 指定了分析函数进行运算的数据子集。开窗子句的具体语法如下:
1 | [rows | range] between <start expr> and <end expr> |
unbounded preceding
表示以分组数据的第一行最为上边界;unbounded following
表示以分组数据的最后一行最为下边界;current row
表示当前数据行;n preceding
表示当前数据行的前n 行;n following
表示当前数据行的后n 行。
为了演示分析函数,首先在SH用户下创建一张sales_fact表:
1 | SQL> create table sales_fact as |
分析模式下的聚合函数
前面所说聚合函数每个分组只能对应一行数据是在传统的非分析模式下的结果。分析模式下的聚合函数则没有此限制。
比如下面的SQL语句计算了sale列按照产品,国家,地区和年份为一组,从每年年初开始到该年份每一周的动态求和值。(为了节约篇幅,下面结果只取前10周结果集)。
1 | SQL> select year,week,sale, |
聚合函数sum(sale)
声明要求和的列,partition by product,country,region,year
分区子句声明了分组的列,order by week
排序子句声明了分组的数据行按照week列来进行排序,rows between unbounded preceding and current row
开窗子句声明了计算的窗口范围为分组的第一行到当前行。
下面例子的窗口范围为整个分组:
1 | SQL> select year,week,sale, |
下面例子的窗口范围为当前周的前两周和后两周,也就是五周。在分组的边界处,窗口会自动缩小:
1 | SQL> select year,week,sale, |
默认的窗口子句是rows between unbounded preceding and current row
。
lag和lead
lag和lead函数能够实现跨行引用。lag能够访问结果集中前面行内容,lead能够访问结果集中后面行内容。
lag和lead函数不支持开窗子句,仅支持partition by
和order by
子句。
lag
lag函数的语法如下:
1 | lag(expression, offset, default) over(partition-clause order-by-clause) |
expression
表示返回的列,offset
表示相隔的行数(不能为负数),default表示默认值。
比如,从前一行中返回一个值:
1 | SQL> select year,week,sale, |
可见lag函数在分区的上边界返回空值,默认行数为1。
指定lag函数的默认值,并指定行数为3:
1 | SQL> select year,week,sale, |
lead
lead和lag类似。不再赘述。
first_value和last_value
first_value和last_value函数通常与order by语句配合来筛选出分区中的最大值和最小值。它们都支持开窗子句。
first_value
first_value返回窗口中的第一个值。ignore nulls表示忽略空值,如果第一个是空值返回第二个:
1 | SQL> select year,week,sale, |
last_value
last_value返回窗口中的最后一个值。respect nulls表示识别空值,如果最后一个是空值也将其返回。
1 | SQL> select year,week,sale, |
nth_value
nth_value函数用于返回任意行的数据,语法如下:
1 | nth_value(measure, n) [from first | from last] [respect nulls | ignore nulls] |
比如first_value(sale)
等价于nth_value(sale,1)
;first_value(sale ignore nulls)
等价于nth_value(sale,1) from first ignore nulls
。
比如求得分区内第二大的sale值大小:
1 | SQL> select year,week,sale, |
rank
rank函数以数值的形式返回一个数据行在排序后的结果集中的位置。在排名并列的情况下,具有相同值的行将具有相同的排名,并且接下来的排名会被跳过。
rank函数不支持开窗子句,作用于整个分区:
1 | SQL> select * from ( |
dense_rank
dense_rank函数是rank函数的变体,区别在于dense_rank函数的排名值是连续的:
1 | SQL> select * from ( |
row_number
row_number函数为结果集中的每一行分配一个递增行编号,支持开窗子句。如果存在值相同的数据行,谁先谁后具有不确定性。
1 | SQL> select year,week,sale, |
ratio_to_report
ratio_to_report函数用于计算当前行的值占分区总和的值的百分比,该函数没有排序和开窗子句。
比如计算当前周的销售额在该年以及所有销售额中的百分比:
1 | SQL> select year,week,sale, |
ntile
ntile函数对一个分区中的有序结果集进行划分,分为若干个组,如果不能够等分,则每个组中相差的数据行不能超过一行,并为每个小组分配唯一的组编号。该函数不支持开窗子句。
比如将2001年的销售额数据行分为十个小组:
1 | SQL> select year,week,sale, |
stddev
stddev函数用于计算某些数据行在分区中的标准差:
1 | SQL> select year,week,sale, |
listagg
listagg函数将分区中多个数据行中的某列的值以某个符号拼接成一行,语法如下:
1 | listagg(string,separator) within group(order-by-clause) over(partition-by-clause) |
within group(order-by-clause)
子句声明排序顺序。
比如将sales_fact数据表中的country转化为以逗号分隔的一行值:
1 | SQL> col country_string for a50 |